// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
use rusqlite::{Connection, params};
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::Read;
use std::sync::Mutex;
use std::path::Path;
use std::collections::HashMap;
use tauri::State;

// Define structs to match the JSON structure
#[derive(Serialize, Deserialize, Debug)]
struct Acupoint {
    no: String,
    name: String,
    link: String,
    location: Option<String>,
    detail: Option<String>,
    description: Option<String>,
    image_url: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Meridian {
    name: String,
    no: String,
    link: String,
    pic: String,
    introduction: String,
    acupoints: Vec<Acupoint>,
}

// Define a database connection wrapper
struct DbConnection(Mutex<Connection>);

// Initialize the database
fn init_database() -> Connection {
    let conn = Connection::open("meridians.db").expect("Failed to open database");
    
    // Create tables
    conn.execute(
        "CREATE TABLE IF NOT EXISTS meridians (
            id INTEGER PRIMARY KEY,
            no TEXT NOT NULL,
            name TEXT NOT NULL,
            link TEXT NOT NULL,
            pic TEXT NOT NULL,
            introduction TEXT NOT NULL
        )",
        [],
    ).expect("Failed to create meridians table");

    conn.execute(
        "CREATE TABLE IF NOT EXISTS acupoints (
            id INTEGER PRIMARY KEY,
            meridian_id INTEGER NOT NULL,
            no TEXT NOT NULL,
            name TEXT NOT NULL,
            link TEXT NOT NULL,
            location TEXT,
            detail TEXT,
            description TEXT,
            image_url TEXT,
            FOREIGN KEY (meridian_id) REFERENCES meridians(id)
        )",
        [],
    ).expect("Failed to create acupoints table");

    conn
}

// Load data from JSON and populate the database
fn load_data_to_db(conn: &mut Connection) -> Result<(), String> {
    // Check if data is already loaded
    {
        let row_count: i64 = conn.query_row(
            "SELECT COUNT(*) FROM meridians",
            [],
            |row| row.get(0)
        ).expect("Failed to count meridians");
        
        if row_count > 0 {
            return Ok(());  // Data already loaded
        }
    }
    
    // Read and parse JSON file
    let meridians = load_json_data()?;
    
    // Insert data into database
    insert_meridians_to_db(conn, &meridians)?;
    
    Ok(())
}

// Load meridian data from JSON file
fn load_json_data() -> Result<Vec<Meridian>, String> {
    // Try several possible locations for the JSON file
    let possible_paths = vec![
        Path::new("acupoints.json"),
        Path::new("../acupoints.json"),
        Path::new("../../acupoints.json"),
        Path::new("../../../acupoints.json"),
        Path::new("src-tauri/acupoints.json"),
        Path::new("src-tauri/../acupoints.json")
    ];
    
    let mut file_result = Err(String::from("File not found in any location"));
    let mut found_path = String::from("Unknown");
    
    // Try each path until we find one that works
    for path in possible_paths {
        match File::open(path) {
            Ok(file) => {
                found_path = format!("{:?}", path);
                file_result = Ok(file);
                break;
            },
            Err(_) => continue,
        }
    }
    
    // If we couldn't find the file, return the error
    let mut file = file_result?;
    
    println!("Found acupoints.json at: {}", found_path);
    
    // Read the file content
    let mut contents = String::new();
    file.read_to_string(&mut contents)
        .map_err(|e| format!("Failed to read JSON file: {}", e))?;
    
    // Parse the JSON data
    let meridians: Vec<Meridian> = serde_json::from_str(&contents)
        .map_err(|e| format!("Failed to parse JSON: {}", e))?;
    
    Ok(meridians)
}

// Insert meridian data into database
fn insert_meridians_to_db(conn: &mut Connection, meridians: &[Meridian]) -> Result<(), String> {
    // Start a transaction
    let tx = conn.transaction().expect("Failed to start transaction");
    
    for meridian in meridians {
        // Insert meridian
        tx.execute(
            "INSERT INTO meridians (no, name, link, pic, introduction) VALUES (?1, ?2, ?3, ?4, ?5)",
            params![meridian.no, meridian.name, meridian.link, meridian.pic, meridian.introduction],
        ).expect("Failed to insert meridian");
        
        // Get the meridian ID
        let meridian_id = tx.last_insert_rowid();
        
        // Insert acupoints
        for acupoint in &meridian.acupoints {
            tx.execute(
                "INSERT INTO acupoints (meridian_id, no, name, link, location, detail, description, image_url)
                 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
                params![
                    meridian_id,
                    acupoint.no,
                    acupoint.name,
                    acupoint.link,
                    acupoint.location,
                    acupoint.detail,
                    acupoint.description,
                    acupoint.image_url
                ],
            ).expect("Failed to insert acupoint");
        }
    }
    
    // Commit the transaction
    tx.commit().expect("Failed to commit transaction");
    
    Ok(())
}

// API Commands
#[tauri::command]
fn get_all_meridians(db: State<DbConnection>) -> Result<Vec<HashMap<String, serde_json::Value>>, String> {
    let conn = db.0.lock().unwrap();
    let mut stmt = conn.prepare("SELECT id, no, name, pic FROM meridians ORDER BY id").expect("Failed to prepare statement");
    
    let rows = stmt.query_map([], |row| {
        let mut map = HashMap::new();
        map.insert("id".to_string(), serde_json::to_value(row.get::<_, i64>(0).unwrap()).unwrap());
        map.insert("no".to_string(), serde_json::to_value(row.get::<_, String>(1).unwrap()).unwrap());
        map.insert("name".to_string(), serde_json::to_value(row.get::<_, String>(2).unwrap()).unwrap());
        map.insert("pic".to_string(), serde_json::to_value(row.get::<_, String>(3).unwrap()).unwrap());
        Ok(map)
    }).expect("Failed to query meridians");
    
    let mut result = Vec::new();
    for row in rows {
        result.push(row.expect("Failed to read row"));
    }
    
    Ok(result)
}

#[tauri::command]
fn get_meridian_detail(db: State<DbConnection>, meridian_id: i64) -> Result<HashMap<String, serde_json::Value>, String> {
    let conn = db.0.lock().unwrap();
    let mut stmt = conn.prepare("SELECT id, no, name, link, pic, introduction FROM meridians WHERE id = ?").expect("Failed to prepare statement");
    
    let meridian = stmt.query_row(params![meridian_id], |row| {
        let mut map = HashMap::new();
        map.insert("id".to_string(), serde_json::to_value(row.get::<_, i64>(0).unwrap()).unwrap());
        map.insert("no".to_string(), serde_json::to_value(row.get::<_, String>(1).unwrap()).unwrap());
        map.insert("name".to_string(), serde_json::to_value(row.get::<_, String>(2).unwrap()).unwrap());
        map.insert("link".to_string(), serde_json::to_value(row.get::<_, String>(3).unwrap()).unwrap());
        map.insert("pic".to_string(), serde_json::to_value(row.get::<_, String>(4).unwrap()).unwrap());
        map.insert("introduction".to_string(), serde_json::to_value(row.get::<_, String>(5).unwrap()).unwrap());
        Ok(map)
    }).expect("Failed to get meridian detail");
    
    Ok(meridian)
}

#[tauri::command]
fn get_meridian_acupoints(db: State<DbConnection>, meridian_id: i64) -> Result<Vec<HashMap<String, serde_json::Value>>, String> {
    let conn = db.0.lock().unwrap();
    let mut stmt = conn.prepare("SELECT id, no, name, location, image_url FROM acupoints WHERE meridian_id = ? ORDER BY id").expect("Failed to prepare statement");
    
    let rows = stmt.query_map(params![meridian_id], |row| {
        let mut map = HashMap::new();
        map.insert("id".to_string(), serde_json::to_value(row.get::<_, i64>(0).unwrap()).unwrap());
        map.insert("no".to_string(), serde_json::to_value(row.get::<_, String>(1).unwrap()).unwrap());
        map.insert("name".to_string(), serde_json::to_value(row.get::<_, String>(2).unwrap()).unwrap());
        map.insert("location".to_string(), serde_json::to_value(row.get::<_, Option<String>>(3).unwrap()).unwrap());
        map.insert("image_url".to_string(), serde_json::to_value(row.get::<_, Option<String>>(4).unwrap()).unwrap());
        Ok(map)
    }).expect("Failed to query acupoints");
    
    let mut result = Vec::new();
    for row in rows {
        result.push(row.expect("Failed to read row"));
    }
    
    Ok(result)
}

#[tauri::command]
fn get_acupoint_detail(db: State<DbConnection>, acupoint_id: i64) -> Result<HashMap<String, serde_json::Value>, String> {
    let conn = db.0.lock().unwrap();
    let mut stmt = conn.prepare(
        "SELECT a.id, a.no, a.name, a.link, a.location, a.detail, a.description, a.image_url, m.id as meridian_id, m.name as meridian_name 
         FROM acupoints a 
         JOIN meridians m ON a.meridian_id = m.id 
         WHERE a.id = ?"
    ).expect("Failed to prepare statement");
    
    let acupoint = stmt.query_row(params![acupoint_id], |row| {
        let mut map = HashMap::new();
        map.insert("id".to_string(), serde_json::to_value(row.get::<_, i64>(0).unwrap()).unwrap());
        map.insert("no".to_string(), serde_json::to_value(row.get::<_, String>(1).unwrap()).unwrap());
        map.insert("name".to_string(), serde_json::to_value(row.get::<_, String>(2).unwrap()).unwrap());
        map.insert("link".to_string(), serde_json::to_value(row.get::<_, String>(3).unwrap()).unwrap());
        map.insert("location".to_string(), serde_json::to_value(row.get::<_, Option<String>>(4).unwrap()).unwrap());
        map.insert("detail".to_string(), serde_json::to_value(row.get::<_, Option<String>>(5).unwrap()).unwrap());
        map.insert("description".to_string(), serde_json::to_value(row.get::<_, Option<String>>(6).unwrap()).unwrap());
        map.insert("image_url".to_string(), serde_json::to_value(row.get::<_, Option<String>>(7).unwrap()).unwrap());
        map.insert("meridian_id".to_string(), serde_json::to_value(row.get::<_, i64>(8).unwrap()).unwrap());
        map.insert("meridian_name".to_string(), serde_json::to_value(row.get::<_, String>(9).unwrap()).unwrap());
        Ok(map)
    }).expect("Failed to get acupoint detail");
    
    Ok(acupoint)
}

#[tauri::command]
fn search(db: State<DbConnection>, query: String) -> Result<HashMap<String, Vec<HashMap<String, serde_json::Value>>>, String> {
    let conn = db.0.lock().unwrap();
    let query_param = format!("%{}%", query);
    
    // Search meridians
    let mut meridians_stmt = conn.prepare(
        "SELECT id, no, name, pic FROM meridians WHERE name LIKE ? OR no LIKE ? ORDER BY id LIMIT 10"
    ).expect("Failed to prepare meridians statement");
    
    let meridian_rows = meridians_stmt.query_map(params![query_param, query_param], |row| {
        let mut map = HashMap::new();
        map.insert("id".to_string(), serde_json::to_value(row.get::<_, i64>(0).unwrap()).unwrap());
        map.insert("no".to_string(), serde_json::to_value(row.get::<_, String>(1).unwrap()).unwrap());
        map.insert("name".to_string(), serde_json::to_value(row.get::<_, String>(2).unwrap()).unwrap());
        map.insert("pic".to_string(), serde_json::to_value(row.get::<_, String>(3).unwrap()).unwrap());
        map.insert("type".to_string(), serde_json::to_value("meridian").unwrap());
        Ok(map)
    }).expect("Failed to query meridians");
    
    let mut meridians_result = Vec::new();
    for row in meridian_rows {
        meridians_result.push(row.expect("Failed to read meridian row"));
    }
    
    // Search acupoints
    let mut acupoints_stmt = conn.prepare(
        "SELECT a.id, a.no, a.name, a.image_url, m.id as meridian_id, m.name as meridian_name 
         FROM acupoints a 
         JOIN meridians m ON a.meridian_id = m.id 
         WHERE a.name LIKE ? OR a.no LIKE ? 
         ORDER BY a.id LIMIT 10"
    ).expect("Failed to prepare acupoints statement");
    
    let acupoint_rows = acupoints_stmt.query_map(params![query_param, query_param], |row| {
        let mut map = HashMap::new();
        map.insert("id".to_string(), serde_json::to_value(row.get::<_, i64>(0).unwrap()).unwrap());
        map.insert("no".to_string(), serde_json::to_value(row.get::<_, String>(1).unwrap()).unwrap());
        map.insert("name".to_string(), serde_json::to_value(row.get::<_, String>(2).unwrap()).unwrap());
        map.insert("image_url".to_string(), serde_json::to_value(row.get::<_, Option<String>>(3).unwrap()).unwrap());
        map.insert("meridian_id".to_string(), serde_json::to_value(row.get::<_, i64>(4).unwrap()).unwrap());
        map.insert("meridian_name".to_string(), serde_json::to_value(row.get::<_, String>(5).unwrap()).unwrap());
        map.insert("type".to_string(), serde_json::to_value("acupoint").unwrap());
        Ok(map)
    }).expect("Failed to query acupoints");
    
    let mut acupoints_result = Vec::new();
    for row in acupoint_rows {
        acupoints_result.push(row.expect("Failed to read acupoint row"));
    }
    
    // Combine results
    let mut result = HashMap::new();
    result.insert("meridians".to_string(), meridians_result);
    result.insert("acupoints".to_string(), acupoints_result);
    
    Ok(result)
}

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let mut conn = init_database();
    match load_data_to_db(&mut conn) {
        Ok(_) => println!("Database initialized successfully"),
        Err(e) => eprintln!("Error loading data to database: {}", e),
    }

    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .manage(DbConnection(Mutex::new(conn)))
        .invoke_handler(tauri::generate_handler![
            greet,
            get_all_meridians,
            get_meridian_detail,
            get_meridian_acupoints,
            get_acupoint_detail,
            search
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
